/************************* SERVEUR TCP **************************
 * Nom ......... : ServerSelect.c                                *
 * Role ........ : Server de chat. Le code permet les            *
 *                 différentes interactions entre ses clients    *
 * Auteur ...... : Niroj Edward & Jonas Marquis                  *
 * Version ..... : V1 du 01/01/2014                              *
 * Licence ..... : GPL                                           *
 *                                                               *
 * Compilation :                                                 *
 * gcc -pthread -o Server ServerSelect.c                         *
 * Pour exécuter, tapez : ./Server                               *
 ****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <signal.h> 
#include <pthread.h>

#define PORTS "55555"
#define TAILLE_BUF 256

/* ---------- Variables globales ---------- */
int sockfd, new_fd, rv;
int fdmax; // maximum file descriptor number
char cClientsLogins[20];
int iClientsSockets[20];
int iNum_ClientMax;

typedef struct prm_thread { // Données propres aux threads
    int iActual_Pthread; //ID de la thread courante
    int iPrevious_Pthread; //ID de la thread précédente
    int iNum_Client; //ID du client : référencé dans le tableau
    int iNum_Socket; //Numéro de socket
    int iNum_Dest_Client; //ID du client destinataire : référencé dans le tableau
    int iNum_Dest_Socket; //Numéro de socket du destinataire
    int iTailleMessage; // Taille des données à envoyés
    char cBuffer;
} prm_thread;

struct addrinfo hints, *servinfo, *p;
struct sockaddr their_adr;

socklen_t addrlen;
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()

/* ---------- Traitement des messages ---------- */
void typeMessage(prm_thread *prm) {
    char cType[1], cDimData[4];
    char cLogin[2], cNameLogin[50];
    int iDimData;
    int iLogin;
    int iType;
    int i;

    iType = 0;
    iDimData = 0;

    snprintf(cType, 2, "%s", prm->cBuffer);
    iType = atoi(cType);
    printf("Type de message : %d \n", iType);

    switch (iType) {

        case 1: //iType : un message

            pthread_join(prm->iPrevious_Pthread, NULL);
            envoi_Aux_Clients((void *) &prm);
            break;

        case 2: //iType : un fichier
            snprintf(cLogin, 4, "%s", prm->cBuffer + 1);
            iLogin = atoi(cLogin);
            printf("ID : %d \n", iLogin);
            pthread_join(prm->iPrevious_Pthread, NULL);
            if (iLogin == 0) {
                envoi_Aux_Clients((void *) &prm);
            } else {
                prm->iNum_Dest_Socket = iClientsSockets[iLogin];
                envoi_A_Un_Client((void *) &prm);
            }
            break;

        case 3: //iType : une connexion

            /* Formation de la trame à la réception
             * 3Falcom92
             * Type = 3
             * Login = Falcom92
             */

            iLogin = iNum_ClientMax;
            printf("ID : %d \n", iLogin);
            sprintf(cNameLogin, "%s", prm->cBuffer + 1);
            printf("Login : %s \n", cNameLogin);

            sprintf(cClientsLogins[iNum_ClientMax], "%s", cNameLogin);
            iClientsSockets[iNum_ClientMax] = prm->iNum_Socket;

            /* Formation de la trame à l'envoi
             * 319Falcom92
             * ID Client = 19
             * Type = 3
             * Login = Falcom92
             */

            printf("ID : %d", iNum_ClientMax);
            if (iNum_ClientMax < 10) {
                sprintf(cLogin, "0%d", iNum_ClientMax);
            } else {
                sprintf(cLogin, "%d", iNum_ClientMax);
            }
            printf("Login : %s\n", cNameLogin);
            sprintf(prm->cBuffer, "%d%s%s", iType, cLogin, cNameLogin);
            printf("Chaine : %s \n\n", prm->cBuffer);

            iNum_ClientMax = iNum_ClientMax + 1;
            pthread_join(prm->iPrevious_Pthread, NULL);
            envoi_Aux_Clients((void *) &prm);

            prm->iNum_Dest_Socket = prm->iNum_Socket;
            for (i = 1; i <= iNum_ClientMax - 1; i++) {
                sprintf(prm->cBuffer, "%d%d%s", iType, i, cClientsLogins[i]);
                envoi_A_Un_Client((void *) &prm);
            }
            break;

        default:
            printf("+--------- Processus typeMessage() --------+\n"
                    "| Erreur rencontrée. Type non-reconnu      |\n"
                    "| Logs des paramètres :                    |\n"
                    "| Type = %d                                |\n"
                    "+------------------- EOL ------------------+\n\n",
                    iType);
    }
}

/* ---------- Envoi des données ---------- */

// Client Unique

void envoi_A_Un_Client(prm_thread *prm) {
    if (FD_ISSET(prm->iNum_Dest_Socket, &master)) {
        send(prm->iNum_Dest_Socket, prm->cBuffer, sizeof prm->cBuffer, 0);
        printf("******* Processus envoi_A_Un_Client() ******\n"
                "* Envoi de données.                        *\n"
                "* Destinataire = %d                        *\n"
                "* Socket = %d                              *\n"
                "* Trame = %s                               *\n"
                "******************** EOL *******************\n\n",
                prm->iNum_Dest_Client, prm->iNum_Dest_Socket, prm->cBuffer);
    }
}

// Clients Multiples

void envoi_Aux_Clients(prm_thread *prm) {
    int i;
    i = 1;
    
    printf("******* Processus envoi_Aux_Clients() ******\n"
            "* Envoi de données.                        *\n");
    
    for (i = 1; i <= iNum_ClientMax; i++) { // Envoie données à tous les clients
        if (FD_ISSET(iClientsSockets[i], &master)) {
            if (iClientsSockets[i] != prm->iNum_Socket) { // Sauf client source
                send(iClientsSockets[i], prm->cBuffer, sizeof prm->cBuffer, 0);
                
                printf("* Destinataire = %d                        *\n"
                        "* Socket = %d                              *\n",
                        i, iClientsSockets[i]);
            }
        }
    }
    printf("* Trame = %s                               *\n"
            "******************** EOL *******************\n\n", prm->cBuffer);
}

/* ---------- Processus Communs ---------- */
void f_thread(prm_thread *prm) {
    int nbytes;

    if ((nbytes = recv(prm->iActual_Pthread, prm->cBuffer, sizeof prm->cBuffer, 0)) <= 0) { // erreur ou connexion fermée par client
        if (nbytes == 0) {
            printf(" Fermeture de la connexion : %d\n", prm->iActual_Pthread);
        } else {
            printf("+---------- Processus f_thread() ----------+\n"
                    "| Erreur rencontrée.                       |\n"
                    "| Logs des paramètres :                    |\n"
                    "| Type = %d                                |\n"
                    "| i = %d                                   |\n"
                    "+------------------- EOL ------------------+\n\n",
                    prm->iActual_Pthread);
        }
        close(prm->iActual_Pthread);
        FD_CLR(prm->iActual_Pthread, &master); // Supprime ensemble
    } else { // Données reçu du client
        printf("**** Processus f_thread(prm_thread *prm) ***\n"
                "* Reception de données.                    *\n"
                "* Socket = %d                              *\n"
                "* Trame = %s                               *\n"
                "******************** EOL *******************\n\n",
                prm->iNum_Socket, prm->cBuffer);
        typeMessage((void *) &prm);
    }
}

main() {

    // Déclaration des variables
    int i, j, nbytes, iLocal_Previous_Pthread;
    pthread_t pthread_id;
    prm_thread prm;

    // Initialisation des variables
    i = 0;
    j = 0;
    nbytes = 0;
    iLocal_Previous_Pthread = 0;
    iNum_ClientMax = 1;
    pthread_id = 0;
    prm.iActual_Pthread = 0;
    prm.iPrevious_Pthread = 0;
    prm.iNum_Socket = 0;
    prm.iNum_Client = 0;
    prm.iNum_Dest_Client = 0;
    prm.iNum_Dest_Socket = 0;

    // Clear the master and temp sets
    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    // Création socket et attachement
    memset(&hints, 0, sizeof (hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    printf("************************** SERVEUR TCP **************************\n"
            "* Nom ......... : Server                                        *\n"
            "* Role ........ : Server de chat. Le code permet les            *\n"
            "*                 différentes interactions entre ses clients    *\n"
            "* Auteur ...... : Niroj Edward & Jonas Marquis                  *\n"
            "* Version ..... : V1 du 05/01/2014                              *\n"
            "* Licence ..... : GPL                                           *\n"
            "*****************************************************************\n\n"
            "Logs des evenements : \n");

    // use my IP
    rv = getaddrinfo(NULL, PORTS, &hints, &servinfo);
    printf("Port serveur : %s \n", PORTS);

    if (rv != 0) {
        printf("+------------ Processus main() ------------+\n"
                "| Erreur rencontrée : Addresse.            |\n"
                "| Logs des paramètres :                    |\n"
                "|  - getaddrinfo = %s                      |\n"
                "+------------------- EOL ------------------+\n\n",
                gai_strerror(rv));
        return (1);
    }

    // Création socket et attachement
    for (p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            printf("+------------ Processus main() ------------+\n"
                    "| Erreur rencontrée : Socket.              |\n"
                    "| Logs des paramètres :                    |\n"
                    "|  - sockfd = %d                           |\n"
                    "|  - p = %d                                |\n"
                    "+------------------- EOL ------------------+\n\n",
                    sockfd, p);
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            printf("+------------ Processus main() ------------+\n"
                    "| Erreur rencontrée : Bind.                |\n"
                    "| Logs des paramètres :                    |\n"
                    "|  - sockfd = %d                           |\n"
                    "|  - p = %d                                |\n"
                    "+------------------- EOL ------------------+\n\n",
                    sockfd, p);
            continue;
        }
        break;
    }

    if (p == NULL) {
        printf("+------------ Processus main() ------------+\n"
                "| Erreur rencontrée : failed to bind.      |\n"
                "| Logs des paramètres :                    |\n"
                "|  - p = %d                                |\n"
                "+------------------- EOL ------------------+\n\n", p);
        exit(2);
    }

    freeaddrinfo(servinfo); // Libère structure
    listen(sockfd, 5);

    signal(SIGCHLD, SIG_IGN); //SRV_Parallele

    FD_SET(sockfd, &master); // Ajout sockfd à ensemble
    fdmax = sockfd; // Garde valeur max socket

    while (1) {
        read_fds = master; // copie ensemble
        if (select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1) {
            printf("+------------ Processus main() ------------+\n"
                    "| Erreur rencontrée : Select.              |\n"
                    "| Logs des paramètres :                    |\n"
                    "|  - select = %d                           |\n"
                    "|  - fdmax + 1 = %d                        |\n"
                    "+------------------- EOL ------------------+\n\n",
                    select, fdmax + 1);
            exit(4);
        }

        for (i = 0; i <= fdmax; i++) {

            if (FD_ISSET(i, &read_fds)) {
                if (i == sockfd) {
                    addrlen = sizeof their_adr;
                    new_fd = accept(sockfd, &their_adr, &addrlen);

                    if (new_fd == -1) {
                        printf("+------------ Processus main() ------------+\n"
                                "| Erreur rencontrée : Accept.              |\n"
                                "| Logs des paramètres :                    |\n"
                                "|  - i = %d                                |\n"
                                "|  - new_fd = %d                           |\n"
                                "+------------------- EOL ------------------+\n\n",
                                i, new_fd);
                    } else { // Ajout new_fd à ensemble
                        FD_SET(new_fd, &master);
                        if (new_fd > fdmax) {
                            fdmax = new_fd;
                        }
                        printf("Nouvelle connexion au serveur : %d\n", i);
                    }
                } else { // gestion données client i
                    pthread_t pthread_id = i;
                    prm.iNum_Socket = i;
                    prm.iActual_Pthread = i;
                    prm.iPrevious_Pthread = iLocal_Previous_Pthread;
                    iLocal_Previous_Pthread = i;
                    printf("Creation de la thread : %d\n", i);
                    pthread_create(&pthread_id, NULL, (void *) &f_thread, (void *) &prm); //Creation de la thread
                } // Fin bloc ELSE client
            } // Fin bloc IF FD_ISSET  
        } // Fin bloucle FOR sur i
    } // Fin boucle WHILE
    return 0;
}